/*
 * Copyright (C) 2010 Daniel Sundberg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package nu.danielsundberg.droid.spinbox.objects;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import nu.danielsundberg.droid.spinbox.R;
import nu.danielsundberg.droid.spinbox.R.drawable;
import nu.danielsundberg.droid.spinbox.math.Vertice3f;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;

/**
 * This class is an object representation of 
 * a Room 
 * 
 * @author Daniel Sundberg
 */
public class Room {

	/** The buffer holding the vertices */
	private FloatBuffer vertexBuffer;
	/** The buffer holding the texture coordinates */
	private FloatBuffer textureBuffer;
	/** The buffer holding the indices */
	private ByteBuffer indexBuffer;
	/** The buffer holding the normals */
	private FloatBuffer normalBuffer;
	/** The buffer holding the colors*/
	private FloatBuffer colorBuffer;

	
	/** Our texture pointer */
	private int[] textures = new int[3];


	// Floor
	Vertice3f n0 = new Vertice3f(0.0f, 1.0f, 0.0f);				// Normal Pointing Up
	Vertice3f v0 = new Vertice3f(-10.0f,-10.0f,-10.0f);			// Back Left
	Vertice3f v1 = new Vertice3f(-10.0f,-10.0f, 10.0f);			// Front Left
	Vertice3f v2 = new Vertice3f( 10.0f,-10.0f, 10.0f);			// Front Right
	Vertice3f v3 = new Vertice3f( 10.0f,-10.0f,-10.0f);			// Back Right
	// Ceiling
	Vertice3f n1 = new Vertice3f(0.0f,-1.0f, 0.0f);				// Normal Point Down
	Vertice3f v4 = new Vertice3f(-10.0f, 10.0f, 10.0f);			// Front Left
	Vertice3f v5 = new Vertice3f(-10.0f, 10.0f,-10.0f);			// Back Left
	Vertice3f v6 = new Vertice3f( 10.0f, 10.0f,-10.0f);			// Back Right
	Vertice3f v7 = new Vertice3f( 10.0f, 10.0f, 10.0f);			// Front Right
	// Front Wall
	Vertice3f n2 = new Vertice3f(0.0f, 0.0f, 1.0f);				// Normal Pointing Away From Viewer
	Vertice3f v8 = new Vertice3f(-10.0f, 10.0f,-10.0f);			// Top Left
	Vertice3f v9 = new Vertice3f(-10.0f,-10.0f,-10.0f);			// Bottom Left
	Vertice3f v10 = new Vertice3f(10.0f,-10.0f,-10.0f);			// Bottom Right
	Vertice3f v11 = new Vertice3f(10.0f, 10.0f,-10.0f);			// Top Right
	// Back Wall
	Vertice3f n3 = new Vertice3f(0.0f, 0.0f,-1.0f);				// Normal Pointing Towards Viewer
	Vertice3f v12 = new Vertice3f( 10.0f, 10.0f, 10.0f);			// Top Right
	Vertice3f v13 = new Vertice3f( 10.0f,-10.0f, 10.0f);			// Bottom Right
	Vertice3f v14 = new Vertice3f(-10.0f,-10.0f, 10.0f);			// Bottom Left
	Vertice3f v15 = new Vertice3f(-10.0f, 10.0f, 10.0f);			// Top Left
	// Left Wall
	Vertice3f n4 = new Vertice3f(1.0f, 0.0f, 0.0f);				// Normal Pointing Right
	Vertice3f v16 = new Vertice3f(-10.0f, 10.0f, 10.0f);			// Top Front
	Vertice3f v17 = new Vertice3f(-10.0f,-10.0f, 10.0f);			// Bottom Front
	Vertice3f v18 = new Vertice3f(-10.0f,-10.0f,-10.0f);			// Bottom Back
	Vertice3f v19 = new Vertice3f(-10.0f, 10.0f,-10.0f);			// Top Back
	// Right Wall
	Vertice3f n5 = new Vertice3f(-1.0f, 0.0f, 0.0f);				// Normal Pointing Left
	Vertice3f v20 = new Vertice3f( 10.0f, 10.0f,-10.0f);			// Top Back
	Vertice3f v21 = new Vertice3f( 10.0f,-10.0f,-10.0f);			// Bottom Back
	Vertice3f v22 = new Vertice3f( 10.0f,-10.0f, 10.0f);			// Bottom Front
	Vertice3f v23 = new Vertice3f( 10.0f, 10.0f, 10.0f);			// Top Front


	/** The initial vertex definition */	
	private float vertices[] = {
			//Vertices according to faces
			v0.x, v0.y, v0.z,
			v1.x, v1.y, v1.z,
			v2.x, v2.y, v2.z,
			v3.x, v3.y, v3.z,
			v4.x, v4.y, v4.z,
			v5.x, v5.y, v5.z,
			v6.x, v6.y, v6.z,
			v7.x, v7.y, v7.z,
			v8.x, v8.y, v8.z,
			v9.x, v9.y, v9.z,
			v10.x, v10.y, v10.z,
			v11.x, v11.y, v11.z,
			v12.x, v12.y, v12.z,
			v13.x, v13.y, v13.z,
			v14.x, v14.y, v14.z,
			v15.x, v15.y, v15.z,
			v16.x, v16.y, v16.z,
			v17.x, v17.y, v17.z,
			v18.x, v18.y, v18.z,
			v19.x, v19.y, v19.z,
			v20.x, v20.y, v20.z,
			v21.x, v21.y, v21.z,
			v22.x, v22.y, v22.z,
			v23.x, v23.y, v23.z

	};

	/** 
	 * The initial normals for the lighting calculations 
	 * 
	 * The normals are not necessarily correct from a 
	 * real world perspective, as I am too lazy to write
	 * these all on my own. But you get the idea and see
	 * what I mean if you run the demo.
	 * 0, 1, 3, 0, 3, 2, 		// Face front
			4, 5, 7, 4, 7, 6, 		// Face right
			8, 9, 11, 8, 11, 10, 	// ...
			12, 13, 15, 12, 15, 14, 
			16, 17, 19, 16, 19, 18, 
			20, 21, 23, 20, 23, 22,
	 */
	//	private float normals[] = {
	//				// Normals
	//			calcNormal(v0, v1, v3).x, calcNormal(v0, v1, v3).y, calcNormal(v0, v1, v3).z,			
	//			calcNormal(v0, v3, v2).x, calcNormal(v0, v3, v2).y, calcNormal(v0, v3, v2).z,
	//			calcNormal(v4, v5, v7).x, calcNormal(v4, v5, v7).y, calcNormal(v4, v5, v7).z,
	//			
	//			calcNormal(v4, v7, v6).x, calcNormal(v4, v7, v6).y, calcNormal(v4, v7, v6).z,
	//			calcNormal(v8, v9, v11).x, calcNormal(v8, v9, v11).y, calcNormal(v8, v9, v11).z,
	//			
	//			calcNormal(v8, v11, v10).x, calcNormal(v8, v11, v10).y, calcNormal(v8, v11, v10).z,
	//			calcNormal(v12, v13, v15).x, calcNormal(v12, v13, v15).y, calcNormal(v12, v13, v15).z,
	//			
	//			calcNormal(v12, v15, v14).x, calcNormal(v12, v15, v14).y, calcNormal(v12, v15, v14).z,
	//			calcNormal(v16, v17, v19).x, calcNormal(v16, v17, v19).y, calcNormal(v16, v17, v19).z,
	//			calcNormal(v16, v19, v18).x, calcNormal(v16, v19, v18).y, calcNormal(v16, v19, v18).z,
	//			calcNormal(v20, v21, v23).x, calcNormal(v20, v21, v23).y, calcNormal(v20, v21, v23).z,
	//			calcNormal(v20, v23, v22).x, calcNormal(v20, v23, v22).y, calcNormal(v20, v23, v22).z
	//	};

	private float normals[] = {
			// Normals
			n0.x, n0.y, n0.z, 						
			n0.x, n0.y, n0.z, 
			n0.x, n0.y, n0.z, 
			n0.x, n0.y, n0.z, 

			n1.x, n1.y, n1.z, 
			n1.x, n1.y, n1.z, 
			n1.x, n1.y, n1.z, 
			n1.x, n1.y, n1.z,

			n2.x, n2.y, n2.z, 
			n2.x, n2.y, n2.z, 
			n2.x, n2.y, n2.z, 
			n2.x, n2.y, n2.z,

			n3.x, n3.y, n3.z, 
			n3.x, n3.y, n3.z, 
			n3.x, n3.y, n3.z, 
			n3.x, n3.y, n3.z,

			n4.x, n4.y, n4.z, 
			n4.x, n4.y, n4.z, 
			n4.x, n4.y, n4.z, 
			n4.x, n4.y, n4.z,

			n5.x, n5.y, n5.z, 
			n5.x, n5.y, n5.z, 
			n5.x, n5.y, n5.z, 
			n5.x, n5.y, n5.z,
	};


	/** The initial texture coordinates (u, v) */	
	private float texture[] = {
			//Mapping coordinates for the vertices
			0.0f, 0.0f, 
			0.0f, 1.0f, 
			1.0f, 0.0f, 
			1.0f, 1.0f,

			1.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			0.0f, 0.0f,

			1.0f, 1.0f, 
			1.0f, 0.0f,  
			0.0f, 1.0f,
			0.0f, 0.0f,

			1.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			0.0f, 0.0f,

			1.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			0.0f, 0.0f,

			1.0f, 1.0f, 
			1.0f, 0.0f, 
			0.0f, 1.0f, 
			0.0f, 0.0f, 
	};

	// Colors
	private float color[] = {
			//Mapping color for indicecs
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f, 
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,

			0.0f, 0.4f, 7.0f, 1.0f, 
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,

			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,

			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,

			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,

			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f,
			0.0f, 0.4f, 7.0f, 1.0f
	};
	
	/** The initial indices definition */
	private byte indices[] = {
			// Faces definition
			0, 1, 2, 3, 0, 2, 		// Face up
			4, 5, 6, 4, 6, 7, 		// Face down
			8, 9, 10, 8, 10, 11, 	// Face front
			12, 13, 14, 12, 14, 15, // Face back
			16, 17, 18, 16, 18, 19, // Face left
			20, 21, 22, 20, 22, 23  // Face right
	};








	/**
	 * The Cube constructor.
	 * 
	 * Initiate the buffers.
	 */
	public Room() {
		//
		ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
		byteBuf.order(ByteOrder.nativeOrder());
		vertexBuffer = byteBuf.asFloatBuffer();
		vertexBuffer.put(vertices);
		vertexBuffer.position(0);

//		//
//		byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
//		byteBuf.order(ByteOrder.nativeOrder());
//		textureBuffer = byteBuf.asFloatBuffer();
//		textureBuffer.put(texture);
//		textureBuffer.position(0);

		//
		byteBuf = ByteBuffer.allocateDirect(normals.length * 4);
		byteBuf.order(ByteOrder.nativeOrder());
		normalBuffer = byteBuf.asFloatBuffer();
		normalBuffer.put(normals);
		normalBuffer.position(0);

		//
		indexBuffer = ByteBuffer.allocateDirect(indices.length);
		indexBuffer.put(indices);
		indexBuffer.position(0);
		
		//
		byteBuf = ByteBuffer.allocateDirect(color.length * 4);
		byteBuf.order(ByteOrder.nativeOrder());
		colorBuffer = byteBuf.asFloatBuffer();
		colorBuffer.put(color);
		colorBuffer.position(0);
	}



	/**
	 * The object own drawing function.
	 * Called from the renderer to redraw this instance
	 * with possible changes in values.
	 * 
	 * @param gl - The GL Context
	 * @param filter - Which texture filter to be used
	 */
	public void draw(GL10 gl, int filter) {
		
		//Bind the texture according to the set texture filter
		//gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[filter]);
		
		// set the color of our element
		
		
		//Enable the vertex, texture and normal state
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		//gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		
		//Set the face rotation
		gl.glFrontFace(GL10.GL_CCW);

		//Point to our buffers
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
		gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
		//gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
		gl.glNormalPointer(GL10.GL_FLOAT, 0, normalBuffer);
		
		//Draw the vertices as triangles, based on the Index Buffer information
		gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE, indexBuffer);

		//Disable the client state before leaving
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		//gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
		gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
		
	}

	/**
	 * Load the textures
	 * 
	 * @param gl - The GL Context
	 * @param context - The Activity context
	 */
	public void loadGLTexture(GL10 gl, Context context) {
//		//Get the texture from the Android resource directory
//		InputStream is = context.getResources().openRawResource(R.drawable.crate);
//		Bitmap bitmap = null;
//		try {
//			//BitmapFactory is an Android graphics utility for images
//			bitmap = BitmapFactory.decodeStream(is);
//
//		} finally {
//			//Always clear and close
//			try {
//				is.close();
//				is = null;
//			} catch (IOException e) {
//			}
//		}
//
//		//Generate there texture pointer
//		gl.glGenTextures(3, textures, 0);
//
//		//Create Nearest Filtered Texture and bind it to texture 0
//		gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
//		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
//		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
//		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
//
//		//Create Linear Filtered Texture and bind it to texture 1
//		gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[1]);
//		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
//		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
//		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
//
//		//Create mipmapped textures and bind it to texture 2
//		gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[2]);
//		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
//		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_NEAREST);
//		/*
//		 * This is a change to the original tutorial, as buildMipMap does not exist anymore
//		 * in the Android SDK.
//		 * 
//		 * We check if the GL context is version 1.1 and generate MipMaps by flag.
//		 * Otherwise we call our own buildMipMap implementation
//		 */
//		if(gl instanceof GL11) {
//			gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
//			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
//
//			//
//		} else {
//			buildMipmap(gl, bitmap);
//		}		
//
//		//Clean up
//		bitmap.recycle();
	}

	/**
	 * Our own MipMap generation implementation.
	 * Scale the original bitmap down, always by factor two,
	 * and set it as new mipmap level.
	 * 
	 * Thanks to Mike Miller (with minor changes)!
	 * 
	 * @param gl - The GL Context
	 * @param bitmap - The bitmap to mipmap
	 */
	private void buildMipmap(GL10 gl, Bitmap bitmap) {

		int level = 0;

		int height = bitmap.getHeight();
		int width = bitmap.getWidth();

		while(height >= 1 || width >= 1) {
			//First of all, generate the texture from our bitmap and set it to the according level
			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0);

			//
			if(height == 1 || width == 1) {
				break;
			}

			//Increase the mipmap level
			level++;

			//
			height /= 2;
			width /= 2;
			Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true);

			//Clean up
			bitmap.recycle();
			bitmap = bitmap2;
		}
	}
}